package com.codefish.codefishseckill.utils.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


/**
 * 基于redis的setnx指令实现的分布式锁
 *
 * @author <a href='https://gitee.com/hzf2281'>codefish</a>
 * @version 1.0.0
 * @since 2022/09/02 下午 02:32
 */
@Slf4j
public class RedisDistributedLock implements Lock {
    //redis操作对象
    RedisTemplate<String, Object> redisTemplate;
    //分布式锁的名称，不指定名称则通过uuid生成
    private final String redisLockName;
    //分布式锁在redis中的前缀
    private static final String REDIS_KEY_PREFIX = "distributedLock:";
    //锁重入次数计数前缀
    private static final String REDIS_KEY_STATE_PREFIX = "distributedLock:state:";
    //默认键过期时间
    private static final Integer DEFAULT_LOCK_TTL_SECONDS = 30;
    //lua脚本
    private static final DefaultRedisScript<Boolean> UNLOCK_SCRIPT = new DefaultRedisScript<>();
    private static final DefaultRedisScript<Boolean> LOCK_SCRIPT = new DefaultRedisScript<>();

    //存储线程当前生成的token
    private static final ThreadLocal<String> LOCK_TOKEN = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());

    //初始化lua脚本
    static {
        //加载资源
        ClassPathResource lockLuaResource = new ClassPathResource("lock.lua");
        ClassPathResource unlockLuaResource = new ClassPathResource("unlock.lua");
        String lockStr;
        String unlockStr;
        byte[] buffer = new byte[5 * 1024];
        int len;
        try (InputStream lockInput = lockLuaResource.getInputStream();
             InputStream unlockInput = unlockLuaResource.getInputStream()) {
            len = lockInput.read(buffer);
            lockStr = new String(buffer, 0, len, StandardCharsets.UTF_8);
            len = unlockInput.read(buffer);
            unlockStr = new String(buffer, 0, len, StandardCharsets.UTF_8);
            //初始化加锁的lua脚本
            LOCK_SCRIPT.setScriptText(lockStr);
            LOCK_SCRIPT.setResultType(Boolean.class);
            //初始化解锁的lua脚本
            UNLOCK_SCRIPT.setScriptText(unlockStr);
            UNLOCK_SCRIPT.setResultType(Boolean.class);
        } catch (IOException e) {
            log.error("初始化lua脚本失败");
            e.printStackTrace();
        }
    }

    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String redisLockName) {
        if (StringUtils.isEmpty(redisLockName)) {
            throw new IllegalArgumentException("redis分布式锁的名称不能为空");
        }
        this.redisTemplate = redisTemplate;
        this.redisLockName = redisLockName;
    }

    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate) {
        //未指定锁的名称，通过uuid随机生成锁的名称
        this(redisTemplate, UUID.randomUUID().toString());
    }

    /**
     * 加锁，尝试加锁失败则自旋,直到获取锁成功
     */
    @Override
    public void lock() {

        while (!tryLock()) {
            //获取锁失败则自旋
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //跳出while循环，加锁成功
    }

    /**
     * 解锁
     */
    @Override
    public void unlock() {
        if (doUnlock(LOCK_TOKEN.get())) {
            LOCK_TOKEN.remove();
        }
    }

    /**
     * 尝试加锁
     *
     * @return 加锁是否成功
     */
    @Override
    public boolean tryLock() {
        return doLock(LOCK_TOKEN.get());
    }


    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

//   -------------- redis分布式锁相关的逻辑操作 --------------------

    private String getRedisKey() {
        return REDIS_KEY_PREFIX + redisLockName;
    }

    private String getRedisStateKey() {
        return REDIS_KEY_STATE_PREFIX + redisLockName;
    }

    private boolean doLock(String token) {
        return redisTemplate.execute(LOCK_SCRIPT, Arrays.asList(getRedisKey(), getRedisStateKey()), token, DEFAULT_LOCK_TTL_SECONDS);
    }


    /**
     * unlock逻辑执行lua脚本，保证解锁操作的原子性
     *
     * @param token 当前线程的token
     */
    private boolean doUnlock(String token) {
        return redisTemplate.execute(UNLOCK_SCRIPT, Arrays.asList(getRedisKey(), getRedisStateKey()), token);
    }

}
